package com.hps.demo.netty.server;

import com.alibaba.fastjson.JSON;
import com.hps.demo.entity.Request;
import com.hps.demo.entity.Response;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;

/**
 * @author heps
 * @since 2020/8/15
 */
@Slf4j
@Sharable
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

  private Map<String, Object> serviceMap;


  public NettyServerHandler(Map<String, Object> serviceMap) {
    this.serviceMap = serviceMap;
  }

  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
    log.info("客户端连接成功! {}", ctx.channel().remoteAddress());
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    log.info("客户端连接断开! {}", ctx.channel().remoteAddress());
    ctx.channel().closeFuture();
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    Request request = JSON.parseObject(msg.toString(), Request.class);
    if ("heartBeat".equals(request.getMethodName())) {
      log.info("客户端心跳信息...{}", ctx.channel().remoteAddress());
    } else {
      log.info("RPC客户端请求接口: {}  方法名: {}", request.getClassName(), request.getMethodName());
      Response response = new Response();
      response.setRequestId(request.getId());
      try {
        Object result = this.handler(request);
        response.setData(result);
      } catch (Throwable e) {
        e.printStackTrace();
        response.setCode(1);
        response.setErrorMsg(e.toString());
        log.error("RPC Server handle request error", e);
      }
      ctx.writeAndFlush(response);
    }
  }

  /**
   * 通过反射, 执行本地方法
   *
   * @param request
   * @return
   */
  private Object handler(Request request) throws Throwable {
    String className = request.getClassName();
    Object serviceBean = serviceMap.get(className);
    if (Objects.nonNull(serviceBean)) {
      Class<?> serviceClass = serviceBean.getClass();
      String methodName = request.getMethodName();
      Class<?>[] parameterTypes = request.getParameterTypes();
      Object[] parameters = request.getParameters();

      Method method = serviceClass.getMethod(methodName, parameterTypes);
      method.setAccessible(true);
      return method.invoke(serviceBean, getParameters(parameterTypes, parameters));
    } else {
      throw new Exception("未找到服务接口,请检查配置!:"+className+"#"+request.getMethodName());
    }
  }

  private Object[] getParameters(Class<?>[] parameterTypes, Object[] parameters) {
    if (Objects.isNull(parameters) || parameters.length == 0) {
      return parameters;
    }
    Object[] newParameters = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
      newParameters[i] = JSON.parseObject(parameters[i].toString(), parameterTypes[i]);
    }
    return newParameters;
  }

  @Override
  public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if (evt instanceof IdleStateEvent) {
      IdleStateEvent event = (IdleStateEvent) evt;
      if (event.state()== IdleState.ALL_IDLE){
        log.info("客户端已超过60秒未读写数据,关闭连接.{}",ctx.channel().remoteAddress());
        ctx.channel().close();
      } else {
        super.userEventTriggered(ctx, evt);
      }
    } else {
      super.userEventTriggered(ctx, evt);
    }
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    log.info(cause.getMessage());
    ctx.close();
  }
}
